using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using System.Diagnostics;


namespace PerformanceTimers
{
    /// <summary>
    /// Specifies what mode a <see cref="PerformanceViewer"/> will display its data in.
    /// </summary>
    public enum PerformanceViewerMode
    {
        /// <summary>
        /// Displays the average execution time of each <see cref="PerformanceTracker"/>.
        /// </summary>
        ByTracker,
        /// <summary>
        /// Displays the percentage of time each thread spends in each <see cref="PerformanceTracker"/>.
        /// Entries are organised in a call tree for each thread.
        /// </summary>
        ByThread,
        /// <summary>
        /// Displays the percentage of time spent in each timer, with the number of threads given in brackets.
        /// All threads are merged.
        /// </summary>
        ByThreadMergeAll,
        /// <summary>
        /// Displays the percentage of time spent in each timer, with the number of threads given in brackets.
        /// Threads with the same name are merged.
        /// </summary>
        ByThreadMergeNames
    }

    class StatTree
    {
        public string Name { get; set; }
        public float Value { get; set; }
        public string Format { get; set; }
        public List<StatTree> Children { get; private set; }

        private int mergeCount = 0;

        public StatTree()
        {
            Children = new List<StatTree>();
        }

        public Vector2 Draw(SpriteBatch spriteBatch, SpriteFont font, Vector2 position)
        {
            var name = mergeCount > 0 ? string.Format("{0} ({1})", Name, mergeCount + 1) : Name;
            var value = mergeCount > 0 ? Value / (mergeCount + 1) : Value;
            spriteBatch.DrawString(font, string.Format(Format, name, value), position, Color.White);
            position.Y += font.LineSpacing;

            for (int i = 0; i < Children.Count; i++)
                position.Y = Children[i].Draw(spriteBatch, font, position + new Vector2(10, 0)).Y;

            return position;
        }

        public void Merge(StatTree tree)
        {
            if (this == tree)
                throw new Exception("wtf");
            this.Value += tree.Value;

            for (int i = 0; i < tree.Children.Count; i++)
            {
                MergeChild(tree.Children[i]);
            }

            mergeCount += tree.mergeCount + 1;

            tree.Recycle(false);
        }

        private void MergeChild(StatTree tree)
        {
            for (int i = 0; i < Children.Count; i++)
            {
                if (Children[i].Name == tree.Name)
                {
                    Children[i].Merge(tree);
                    return;
                }
            }

            Children.Add(tree);
        }

        public void Recycle()
        {
            Recycle(true);
        }

        private void Recycle(bool recycleChildren)
        {
            if (recycleChildren)
            {
                for (int i = 0; i < Children.Count; i++)
                    Children[i].Recycle(true);
            }
            Children.Clear();
            mergeCount = 0;
            Pool<StatTree>.Instance.Return(this);
        }
    }

    /// <summary>
    /// A game component which displays the information collected by any <see cref="PerformanceTracker"/> instances,
    /// formatted into a call tree.
    /// </summary>
    public class PerformanceViewer : DrawableGameComponent
    {
        Button switchModeButton;
        SpriteBatch spriteBatch;
        SpriteFont font;
        PerformanceViewerMode mode;
        List<StatTree> trees;

        /// <summary>
        /// Gets or sets the mode.
        /// </summary>
        /// <value>The mode.</value>
        public PerformanceViewerMode Mode
        {
            get { return mode; }
            set
            {
                mode = value;
                UpdateButton();
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="PerformanceViewer"/> class.
        /// </summary>
        /// <param name="game">The Game that the game component should be attached to.</param>
        public PerformanceViewer(Game game, SpriteFont font)
            : base(game)
        {
            if (game == null)
                throw new ArgumentNullException("game");
            if (font == null)
                throw new ArgumentNullException("font");

            this.spriteBatch = new SpriteBatch(game.GraphicsDevice);
            this.trees = new List<StatTree>();
            this.switchModeButton = new Button(font);
            this.font = font;

            Mode = PerformanceViewerMode.ByThread;
        }

        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {
            // TODO: Add your initialization code here
            TrackerInstance.UpdateFrequency = TimeSpan.FromSeconds(0.5);

            switchModeButton.Clicked = () =>
                {
                    var mode = ((int)Mode + 1) % 4;
                    Mode = (PerformanceViewerMode)mode;

                    UpdateButton();
                };

            Mode = PerformanceViewerMode.ByThread;

            base.Initialize();
        }

        private void UpdateButton()
        {
            switchModeButton.Text = "Mode: " + Mode.ToString(); //Enum.GetName(typeof(PerformanceViewerMode), Mode);
            var textSize = font.MeasureString(switchModeButton.Text);
            switchModeButton.Area = new Rectangle(5, 5, (int)textSize.X, (int)textSize.Y);
        }

        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {
            // TODO: Add your update code here
            bool trackersUpdated = TrackerInstance.Update();
            bool switchedMode = switchModeButton.Update();

            if (trackersUpdated || switchedMode)
                CreateTrees();

            base.Update(gameTime);
        }

        private void CreateTrees()
        {
            for (int i = 0; i < trees.Count; i++)
                trees[i].Recycle();
            trees.Clear();

            if (Mode == PerformanceViewerMode.ByTracker)
                CreateTrackerTree();
            else
                CreateThreadTree();

            if (trees.Count == 0)
                return;

            if (Mode == PerformanceViewerMode.ByThreadMergeAll)
            {
                for (int i = trees.Count - 1; i >= 1; i--)
                {
                    trees[i - 1].Merge(trees[i]);
                    trees.RemoveAt(i);
                }

                trees[0].Name = "All Threads";
            }

            if (Mode == PerformanceViewerMode.ByThreadMergeNames)
            {
                for (int i = 0; i < trees.Count; i++)
                {
                    MergeByName(i);
                }
            }
        }

        private void MergeByName(int index)
        {
            var tree = trees[index];
            for (int i = trees.Count - 1; i > index; i--)
            {
                if (trees[i].Name == tree.Name)
                {
                    tree.Merge(trees[i]);
                    trees.RemoveAt(i);
                }
            }
        }

        private void CreateTrackerTree()
        {
            var pool = Pool<StatTree>.Instance;
            foreach (var tracker in PerformanceTracker.Trackers)
            {
                var tree = pool.Get();
                tree.Name = tracker.Name;
                tree.Value = (float)tracker.AverageDuration.TotalMilliseconds;
                tree.Format = "{0, -15} - {1:F2}ms";
                trees.Add(tree);
            }
        }

        private void CreateThreadTree()
        {
            int unnamedCount = 0;
            var pool = Pool<StatTree>.Instance;
            foreach (var group in TrackerInstance.ThreadGroups)
            {
                var tree = pool.Get();
                tree.Name = (group.Key.Name ?? "Unnamed" + unnamedCount++) + ":";
                tree.Value = 0;
                tree.Format = "{0}";
                AddTreeChildren(tree, group.Value.Roots);
                trees.Add(tree);
            }
        }

        private void AddTreeChildren(StatTree parent, List<TrackerInstance> list)
        {
            var pool = Pool<StatTree>.Instance;
            for (int i = 0; i < list.Count; i++)
            {
                var instance = list[i];
                var tree = pool.Get();
                tree.Name = instance.Tracker.Name;
                tree.Value = instance.ThreadUsage;
                tree.Format = "{0, -15} - {1:F1}%";
                parent.Children.Add(tree);
                AddTreeChildren(tree, instance.Children);
            }
        }

        /// <summary>
        /// Called when the DrawableGameComponent needs to be drawn.
        /// </summary>
        /// <param name="gameTime">Time passed since the last call to Draw.</param>
        public override void Draw(GameTime gameTime)
        {
            spriteBatch.Begin(SpriteBlendMode.AlphaBlend);

            switchModeButton.Draw(spriteBatch);

            Vector2 position = new Vector2(switchModeButton.Area.Left, switchModeButton.Area.Bottom);
            for (int i = 0; i < trees.Count; i++)
                position = trees[i].Draw(spriteBatch, font, position);

            spriteBatch.End();

            base.Draw(gameTime);
        }
    }
}